feat(io): implement CacheIO read-cache layer for disk IO#2419
Conversation
|
/label S-waiting-on-review |
Merge Protections🟢 All 3 merge protections satisfied — ready to merge. Show 3 satisfied protections🟢 Require kind label
🟢 Require version label
🟢 Require linked issue for feature/bug PRs
|
There was a problem hiding this comment.
Code Review
This pull request introduces CacheIO, a page-based read-cache IO layer wrapping an underlying disk-backed IO implementation, along with its configuration parameters, an LRU eviction strategy, and unit tests. The review feedback highlights several critical issues that must be addressed: potential Use-After-Free (UAF) vulnerabilities in ReadImpl and DirectReadImpl due to race conditions and lack of page pinning, uninitialized and un-updated this->size_ values that will cause read operations to fail, a potential division-by-zero crash if page_size is configured as zero, and a redundant if-else block during eviction strategy initialization.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new CacheIO<InnerIO> CRTP IO wrapper that adds a page-based in-memory read cache in front of an underlying IO implementation, plus JSON-parseable parameters and unit tests.
Changes:
- Added
CacheIO<InnerIO>template with page caching, LRU eviction, and DirectRead/MultiRead support. - Added
CacheIOParameter(JSON serialization/parsing) andEvictionStrategy/LRUEviction. - Wired
cache_iointo IO parameter parsing and header aggregation; added unit tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/io/io_headers.h | Exposes cache_io.h through the IO header umbrella. |
| src/io/common/io_parameter.cpp | Adds JSON parsing support for type=cache_io parameters. |
| src/io/CMakeLists.txt | Builds cache_io_parameter.cpp as part of the IO object library. |
| src/io/cache_io/eviction_strategy.h | Introduces eviction strategy interface and LRU implementation. |
| src/io/cache_io/cache_io.h | Implements the CacheIO read-cache IO wrapper. |
| src/io/cache_io/cache_io_test.cpp | Adds unit tests for caching, eviction, direct read, multiread, and parameters. |
| src/io/cache_io/cache_io_parameter.h | Declares CacheIOParameter (page size, total cache size, eviction strategy). |
| src/io/cache_io/cache_io_parameter.cpp | Implements JSON (de)serialization for CacheIOParameter. |
| src/inner_string_params.h | Registers the cache_io IO type string. |
ec12104 to
d4e7d6b
Compare
d4e7d6b to
0b45a24
Compare
0b45a24 to
0c604cd
Compare
0c604cd to
33f1b3a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
src/io/cache_io/page.h:62
Page::SetPageSizemutates a process-wide page size after pages may already have been allocated. SincePageinstances don’t store their own allocation size, changingpage_size_can make later reads/copies compute offsets using a new size against buffers allocated with an old size, leading to out-of-bounds memory access/corruption (e.g., multipleCacheIOinstances with different page sizes, or tests changing page size between runs). Consider making page size immutable (set-once) or storing the page size perPage/CacheIOinstance instead of a mutable static.
static void
SetPageSize(uint64_t page_size) {
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
page_size_ = page_size;
}
src/io/cache_io/cache_io.h:76
WriteImplupdates the inner IO but doesn’t invalidate cached pages that overlap the written range. If the same region was previously read and cached, subsequent reads can return stale data. Unless the entire library guarantees non-overlapping read/write regions, it’s safer to evict affected pages on write to preserve read-after-write coherence.
void
WriteImpl(const uint8_t* data, uint64_t size, uint64_t offset) {
inner_io_->WriteImpl(data, size, offset);
if (offset + size > this->size_) {
this->size_ = offset + size;
}
}
src/io/cache_io/cache_io.h:117
- The PR description states
DirectReadshould return an internal cache pointer for a single-page hit (need_release=false), but this implementation always allocates/copies and setsneed_release=true. Also, returning a raw pointer into a cached page would require a pin/unpin mechanism to keep thePagealive untilRelease(); otherwise eviction could free the page while the caller still uses the pointer. Either update the PR description to match the safe allocate/copy behavior, or introduce a pinned-handle design that makes returning internal pointers safe.
[[nodiscard]] const uint8_t*
DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const {
need_release = false;
if (size == 0 or not is_valid_range(size, offset)) {
return nullptr;
}
auto* data = reinterpret_cast<uint8_t*>(this->allocator_->Allocate(size));
if (data == nullptr) {
return nullptr;
}
if (not ReadImpl(size, offset, data)) {
this->allocator_->Deallocate(data);
return nullptr;
}
need_release = true;
return data;
}
src/io/cache_io/cache_io.h:55
CacheIOParameterexposeseviction_strategy_, butCacheIOcurrently always constructsLRUPageCacheand never consults the parameter. This makes the configuration knob misleading and doesn’t match the PR description’s “pluggable eviction strategy” design. Consider either wiring strategy selection inCacheIO(even if onlylruis supported for now) or removing/deferring the parameter until multiple strategies exist.
CacheIO(const CacheIOParameter& param, Allocator* allocator, Args&&... args)
: BasicIO<CacheIO<IOTmpl>>(allocator),
cache_(std::make_unique<LRUPageCache>(std::max(
uint64_t(1), param.total_cache_size_ / std::max(uint64_t(1), param.page_size_)))),
inner_io_(std::make_unique<IOTmpl>(std::forward<Args>(args)...)) {
Page::SetPageSize(std::max(uint64_t(1), param.page_size_));
this->size_ = inner_io_->size_;
src/io/common/io_parameter.cpp:66
IOParameter::GetIOParameterByJsonnow parsestype: cache_io, but other runtime IO-type validation/selection paths do not recognize it (e.g.,src/factory/factory.cpp:is_valid_streaming_io_typeonly allows memory/block_memory/buffer/async/mmap/reader). This can lead to configuration that parses successfully but is later rejected or cannot be instantiated. Please ensure all IO factories/validators that consumeIOParameterare updated to supportcache_io, or avoid advertising it as a JSON-selectable type until it can be constructed end-to-end.
} else if (type_name == IO_TYPE_VALUE_MMAP_IO) {
io_ptr = std::make_shared<MMapIOParameter>();
io_ptr->FromJson(json);
} else if (type_name == IO_TYPE_VALUE_CACHE_IO) {
io_ptr = std::make_shared<CacheIOParameter>();
io_ptr->FromJson(json);
} else if (type_name == IO_TYPE_VALUE_READER_IO) {
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1051 changed lines across 15 files).
Submitted 5 inline comments.
Reviewed commit df9d769.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/io/cache_io/page.h:67
Pageuses a process-wide staticpage_size_that is mutated byCacheIOconstructors. Creating multipleCacheIOinstances with differentpage_sizevalues (or tests that callPage::SetPageSize) can make existing cachedPagebuffers and page-id calculations inconsistent, risking out-of-bounds reads/copies.
static void
SetPageSize(uint64_t page_size) {
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
page_size_ = page_size;
}
static uint64_t
PageSize() {
return page_size_;
}
src/io/cache_io/cache_io.h:76
WriteImplupdates the inner IO but never invalidates cached pages. If a caller reads data into the cache, then overwrites the same region viaWrite, subsequent reads can return stale cached bytes. Either enforce the “no overlapping reads/writes” precondition at runtime or invalidate the affected pages on write.
void
WriteImpl(const uint8_t* data, uint64_t size, uint64_t offset) {
inner_io_->WriteImpl(data, size, offset);
if (offset + size > this->size_) {
this->size_ = offset + size;
}
}
src/io/cache_io/cache_io.h:117
DirectReadImplalways allocates/copies and setsneed_release=true. This contradicts the PR description that single-page direct reads should return an internal cache pointer withneed_release=false. If the intended contract is “always allocate”, the PR description/tests should be updated; if the intended contract is “return cache pointer”, the implementation needs a safe pinning/lifetime strategy to prevent eviction from invalidating the returned pointer.
[[nodiscard]] const uint8_t*
DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const {
need_release = false;
if (size == 0 or not is_valid_range(size, offset)) {
return nullptr;
}
auto* data = reinterpret_cast<uint8_t*>(this->allocator_->Allocate(size));
if (data == nullptr) {
return nullptr;
}
if (not ReadImpl(size, offset, data)) {
this->allocator_->Deallocate(data);
return nullptr;
}
need_release = true;
return data;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 10 comments.
Comments suppressed due to low confidence (1)
src/algorithm/ivf/ivf.cpp:49
- The default IVF JSON template injects
CACHE_IO_*fields into IO param blocks even when{TYPE_KEY}is notcache_io(e.g.,memory_io,block_memory_io). This makes the template misleading and can cause unrelated IO parameter parsers to receive cache-specific keys. Prefer only including these fields when the IO type is actuallycache_io(or nest them under an explicitinner_io/cache_ioobject) to keep the template self-consistent.
static constexpr const char* IVF_PARAMS_TEMPLATE =
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1629 changed lines across 25 files).
Submitted 9 inline comments.
Reviewed commit b292577.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
src/io/cache_io/page.h:35
Pageuses a process-wide staticpage_size_that is mutated byPage::SetPageSize(). SincePageallocatesallocator_->Allocate(page_size_)at construction time, changing the global page size later (e.g., by constructing anotherCacheIOwith a differentpage_size_) can make existing cached pages too small/large for subsequent reads, risking out-of-bounds reads/writes and memory corruption.
* All pages share a process-wide uniform page size.
*/
class Page {
public:
explicit Page(Allocator* allocator) : allocator_(allocator) {
data_ = static_cast<uint8_t*>(allocator_->Allocate(page_size_));
}
src/io/cache_io/cache_io.h:100
WriteImpldoes not invalidate cached pages that overlap the written range. If a page was previously read into the cache, a later write to the same region can leave the cache serving stale data on subsequent reads.
void
WriteImpl(const uint8_t* data, uint64_t size, uint64_t offset) {
inner_io_->WriteImpl(data, size, offset);
if (offset + size > this->size_) {
this->size_ = offset + size;
}
}
src/io/cache_io/cache_io.h:141
DirectReadImplalways allocates + copies (setsneed_release=true) and never returns a pointer into the cache on single-page hits. This contradicts the PR description’s stated behavior for DirectRead, and it also means DirectRead has no zero-copy fast path.
[[nodiscard]] const uint8_t*
DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const {
need_release = false;
if (size == 0 or not is_valid_range(size, offset)) {
return nullptr;
}
auto* data = reinterpret_cast<uint8_t*>(this->allocator_->Allocate(size));
if (data == nullptr) {
return nullptr;
}
if (not ReadImpl(size, offset, data)) {
this->allocator_->Deallocate(data);
return nullptr;
}
need_release = true;
return data;
}
src/io/cache_io/cache_io.h:62
CacheIOParameterexposeseviction_strategy_, butCacheIOalways constructsLRUPageCacheand ignores the strategy setting. If only LRU is supported right now, it would be clearer to either remove the parameter or explicitly document/validate that only "lru" is accepted.
CacheIO(const CacheIOParameter& param, Allocator* allocator, Args&&... args)
: BasicIO<CacheIO<IOTmpl>>(allocator),
cache_(std::make_unique<LRUPageCache>(std::max(
uint64_t(1), param.total_cache_size_ / std::max(uint64_t(1), param.page_size_)))),
inner_io_(std::make_unique<IOTmpl>(std::forward<Args>(args)...)) {
Page::SetPageSize(std::max(uint64_t(1), param.page_size_));
this->size_ = inner_io_->size_;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 17 comments.
Comments suppressed due to low confidence (4)
src/io/cache_io/page_cache.cpp:50
- PageCache::Insert can spin forever if PickVictim() returns a page_id that isn't present in pages_ (pages_.size() never decreases). It also calls OnRemove() even when nothing was erased, which can desync the eviction state.
while (pages_.size() >= max_pages_) {
uint64_t victim = PickVictim();
if (victim == UINT64_MAX) {
return page;
}
pages_.erase(victim);
OnRemove(victim);
}
src/io/cache_io/cache_io.h:114
- ReadImpl uses Options::Instance().cache_page_size() without guarding against 0, which would cause division/modulo by zero when computing page_id/page_offset.
}
uint64_t page_size = Options::Instance().cache_page_size();
uint64_t cur_size = 0;
src/io/cache_io/cache_io.h:192
- get_or_load_page() uses Options::Instance().cache_page_size() without guarding against 0, which can lead to division-by-zero behavior when calculating file_offset.
uint64_t page_size = Options::Instance().cache_page_size();
uint64_t file_offset = page_id * page_size;
if (file_offset >= this->size_) {
src/io/cache_io/cache_io.h:147
- DirectReadImpl currently always allocates + copies into a new buffer and sets need_release=true. This contradicts the PR description/design notes that single-page hits should return an internal cache pointer with need_release=false. Either implement a safe page-pinning mechanism (so the cached page stays alive while the caller uses the pointer) or update the design/docs to match the current always-copy behavior.
[[nodiscard]] const uint8_t*
DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const {
need_release = false;
if (size == 0 or not is_valid_range(size, offset)) {
return nullptr;
}
auto* data = reinterpret_cast<uint8_t*>(this->allocator_->Allocate(size));
if (data == nullptr) {
return nullptr;
}
if (not ReadImpl(size, offset, data)) {
this->allocator_->Deallocate(data);
return nullptr;
}
need_release = true;
return data;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (8)
src/datacell/graph_interface.cpp:90
- CacheIOParameter doesn’t define inner_io_parameter_, so this branch won’t compile. Use the parsed string field (inner_io_type_) instead when selecting the CacheIO specialization.
if (cache_param) {
auto inner_type = cache_param->inner_io_type_;
src/datacell/flatten_interface.cpp:310
- CacheIOParameter doesn’t have inner_io_parameter_, so this code won’t compile. Use inner_io_type_ (set from JSON) to choose the CacheIO instantiation.
auto cache_param = std::dynamic_pointer_cast<CacheIOParameter>(param->io_parameter);
if (cache_param) {
auto inner_type = cache_param->inner_io_type_;
src/datacell/bucket_interface.cpp:120
- CacheIOParameter doesn’t define inner_io_parameter_, so this code won’t compile. Use inner_io_type_ to determine which CacheIO specialization to build.
if (io_type_name == IO_TYPE_VALUE_CACHE_IO) {
auto cache_param = std::dynamic_pointer_cast<CacheIOParameter>(param->io_parameter);
if (cache_param) {
auto inner_type = cache_param->inner_io_type_;
if (inner_type == IO_TYPE_VALUE_MMAP_IO) {
src/io/cache_io/cache_io.h:95
- CacheIO(io_param, common_param) doesn’t validate inner_io_type_. If it’s missing/invalid, GetIOParameterByJson can return nullptr and the inner IO construction will fail later without a clear error. Validate inner_io_type_ and the returned inner_io_param and throw a helpful exception early.
JsonType inner_json = cache_param->original_json_;
inner_json[TYPE_KEY].SetString(cache_param->inner_io_type_);
auto inner_io_param = IOParameter::GetIOParameterByJson(inner_json);
inner_io_ = std::make_unique<IOTmpl>(inner_io_param, common_param);
this->size_ = inner_io_->size_;
src/io/cache_io/cache_io.h:78
- CacheIO uses Options::Instance().cache_page_size() as mutable global state (including setting it in the ctor) and also relies on it for page indexing and copies. If multiple CacheIO instances are created with different page sizes, or cache_page_size is changed after pages are allocated, reads can compute offsets/copy sizes with a different page size than Page allocated, risking out-of-bounds access.
page_size > 0 ? page_size : Options::Instance().cache_page_size())))),
inner_io_(std::make_unique<IOTmpl>(std::forward<Args>(args)...)) {
if (page_size > 0) {
Options::Instance().set_cache_page_size(page_size);
}
src/io/cache_io/cache_io.h:135
- PR description says DirectRead should return an internal cache pointer on a single-page hit with need_release=false, but DirectReadImpl always allocates/copies and sets need_release=true. Either update the implementation to match the stated design (including safe lifetime/pinning semantics) or adjust the PR description/tests to reflect the actual behavior.
[[nodiscard]] const uint8_t*
DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const {
need_release = false;
if (size == 0 or not is_valid_range(size, offset)) {
return nullptr;
src/algorithm/hgraph/hgraph_param_mapping.cpp:199
- HGraph exposes cache_page_size parameters (and constants.h declares HGRAPH__CACHE_PAGE_SIZE), and examples/tests set base_cache_page_size/precise_cache_page_size, but map_hgraph_param() doesn’t map any of those external keys into IO_PARAMS_KEY/CACHE_IO_PAGE_SIZE_KEY. As a result, user-provided page sizes are silently ignored and only the template default (131072) applies.
HGRAPH_BASE_CACHE_TOTAL_SIZE,
{
BASE_CODES_KEY,
IO_PARAMS_KEY,
CACHE_IO_TOTAL_CACHE_SIZE_KEY,
src/algorithm/ivf/ivf.cpp:143
- IVF exposes base_cache_page_size/precise_cache_page_size (constants.h declares IVF_*_CACHE_PAGE_SIZE), but CheckAndMappingExternalParam only maps cache total size + inner io type. External page size overrides are never mapped into CACHE_IO_PAGE_SIZE_KEY, so user-provided page sizes are ignored.
IVF_BASE_CACHE_TOTAL_SIZE,
{
BUCKET_PARAMS_KEY,
IO_PARAMS_KEY,
CACHE_IO_TOTAL_CACHE_SIZE_KEY,
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/algorithm/ivf/ivf.cpp:1
- The default IO params template includes
CACHE_IO_TOTAL_CACHE_SIZE_KEYwhile the IO{TYPE_KEY}is notcache_io(here it’smemory_io). If IO parameter parsing is strict (or becomes strict later), these extra keys can become a compatibility hazard, and even if parsing is permissive it makes the templates harder to reason about (keys that don’t apply to the selected type). Consider only emitting cache-specific keys when{TYPE_KEY}iscache_io, or nesting them under a cache-io-specific object so the meaning is unambiguous.
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1479 changed lines across 25 files).
Submitted 6 inline comments.
Reviewed commit ef98da8.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/algorithm/hgraph/hgraph_param_mapping.cpp:1
- The HGraph params template adds
CACHE_IO_TOTAL_CACHE_SIZE_KEYunder IO params even when the IO type is{IO_TYPE_VALUE_BLOCK_MEMORY_IO}. Besides the same “unknown key” concern as IVF, this snippet also appears to hardcode a slightly different value (268435256) compared to the 256MB default used elsewhere (268435456), which is likely an unintended typo and results in a non-256MB default cache size if/when the key is honored.
// Copyright 2024-present the vsag project
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1474 changed lines across 25 files).
Submitted 7 inline comments.
Reviewed commit 7264845.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
src/algorithm/hgraph/hgraph_param_mapping.cpp:1
- This template injects
{CACHE_IO_TOTAL_CACHE_SIZE_KEY}even though the IO{TYPE_KEY}is{IO_TYPE_VALUE_BLOCK_MEMORY_IO}. Also, the literal value here (268435256) differs from the 256MB default used elsewhere (268435456) and looks like an accidental typo (off by 200 bytes). Recommend removing the cache-only key for non-cache IO types, and if it should remain, correcting the size to268435456for consistency.
// Copyright 2024-present the vsag project
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (1)
src/algorithm/hgraph/hgraph_param_mapping.cpp:1
- The template injects
{CACHE_IO_TOTAL_CACHE_SIZE_KEY}into IO params even when{TYPE_KEY}isblock_memory_io. Also, the value here appears to be268435256(off by 100) while other defaults use268435456. If these keys are intended only forcache_io, they should not appear in non-cache IO templates; and the default size should be consistent (likely268435456).
// Copyright 2024-present the vsag project
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1783 changed lines across 30 files).
Submitted 5 inline comments.
Reviewed commit 6303999.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
src/datacell/flatten_interface.cpp:333
- This exception message drops the actual inner_io_type value, making configuration errors hard to diagnose. Include the value (or explicitly handle the empty-string case) in the error message.
throw VsagException(ErrorType::INVALID_ARGUMENT, "Unsupported CacheIO inner_io_type");
src/datacell/bucket_interface.cpp:133
- This exception message drops the actual inner_io_type value, making configuration errors hard to diagnose. Include the value (or explicitly handle the empty-string case) in the error message.
throw VsagException(ErrorType::INVALID_ARGUMENT, "Unsupported CacheIO inner_io_type");
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1800 changed lines across 30 files).
Submitted 6 inline comments.
Reviewed commit eed155d.
Signed-off-by: LHT129 <tianlan.lht@antgroup.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/io/cache_io/cache_io.h:52
CacheIOdeclaresstatic constexpr bool InMemory = true, but several call sites useIOTmpl::InMemoryto decide whether to treat an IO as disk-backed (e.g.src/datacell/flatten_datacell.h:326usesif constexpr (not IOTmpl::InMemory)to select the MultiRead/IO-stats path). WithInMemory=true,CacheIO<AsyncIO/BufferIO/MMapIO>will be treated as in-memory and skip that disk path, which can disable batching and IO stats in queries even though the backend is still disk-backed.
static constexpr bool InMemory = true;
static constexpr bool UseNonContinuous = not IOTmpl::InMemory;
static constexpr bool SkipDeserialize = IOTmpl::SkipDeserialize;
src/datacell/bucket_datacell.h:43
AdjustBucketCacheIOParamuses a ceil division to computepages_per_bucket, which can make the total cache capacity across all buckets exceed the user-configuredtotal_cache_size_by up to(bucket_count - 1) * Page::DEFAULT_PAGE_SIZE(potentially very large when bucket_count is large). This can violate memory limits unexpectedly.
uint64_t total_pages = cache_param->total_cache_size_ / Page::DEFAULT_PAGE_SIZE;
uint64_t pages_per_bucket = (total_pages + bucket_count - 1) / bucket_count;
adjusted_param->total_cache_size_ = pages_per_bucket * Page::DEFAULT_PAGE_SIZE;
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (1822 changed lines across 30 files).
Submitted 3 inline comments.
Reviewed commit d9527e2.
| } | ||
| auto adjusted_param = std::make_shared<CacheIOParameter>(*cache_param); | ||
| uint64_t total_pages = cache_param->total_cache_size_ / Page::DEFAULT_PAGE_SIZE; | ||
| uint64_t pages_per_bucket = (total_pages + bucket_count - 1) / bucket_count; |
There was a problem hiding this comment.
[P1] Keep the per-bucket split within the global cache budget
Ceiling this quotient assigns at least one page to every bucket whenever any budget exists, so the total becomes ceil(total_pages / bucket_count) * bucket_count. With a one-page budget and the supported 100×100 GNO-IMI layout, touching all buckets can retain 10,000 pages (about 1.22 GiB) instead of 128 KiB; the default 2,048-page budget similarly expands to 10,000 pages. Distribute a fixed number of whole pages while allowing some buckets zero pages, or share one cache across buckets.
| template <typename IOTmpl> | ||
| class CacheIO : public BasicIO<CacheIO<IOTmpl>> { | ||
| public: | ||
| static constexpr bool InMemory = true; |
There was a problem hiding this comment.
[P2] Keep disk-backed CacheIO out of in-memory execution paths
This is true even for AsyncIO, BufferIO, and MMapIO backends. FlattenDataCell::query therefore skips its disk MultiRead path and allocates/copies once per candidate through DirectReadImpl, while HGraph::EstimateMemory counts the entire graph/code capacity rather than the bounded cache. Preserve the inner IO's residency classification and use a separate trait or accounting path for resident cache pages.
|
|
||
| bool | ||
| ReadImpl(uint64_t size, uint64_t offset, uint8_t* data) const { | ||
| if (not is_valid_range(size, offset)) { |
There was a problem hiding this comment.
[P2] Synchronize size validation with concurrent writes
is_valid_range reads inherited size_ before acquiring io_mutex_, while WriteImpl, ResizeImpl, and ShrinkImpl modify it under that mutex. HGraph supports concurrent add/search, so appending codes while searches read existing codes creates an unsynchronized C++ data race; DirectReadImpl also performs this check. Validate using a synchronized size snapshot or under the IO lock.
Summary
Add CacheIO template class that provides a page-based read cache in front of disk-backed IO implementations. All reads go through the cache; writes passthrough directly to the inner IO with no cache involvement.
Closes #2420
Design
Components
Testing